package com.dpiinc.TechPage.repository
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.paging.LivePagedListBuilder
import com.dpiinc.TechPage.AIWriteSettings
import com.dpiinc.TechPage.AWLog
import com.dpiinc.TechPage.api.NoteListApi
import com.dpiinc.TechPage.api.asyncSubscribe
import com.dpiinc.TechPage.constants.DeleteType
import com.dpiinc.TechPage.model.NoteListBean
import com.dpiinc.TechPage.room.RoomAiWriterDatabase
import com.dpiinc.TechPage.room.bean.NoteEntity
import com.dpiinc.TechPage.utils.DateUtils
import java.util.concurrent.Executors


/**
 * Repository
 * Created by X on 2018/7/1.
 */
class NoteListRepository(
        val db: RoomAiWriterDatabase,
        private val noteListApi: NoteListApi,
        private val networkPageSize: Int = DEFAULT_NETWORK_PAGE_SIZE) {
    companion object {
        private const val DEFAULT_NETWORK_PAGE_SIZE = 20
    }

    // thread pool used for disk access
    @Suppress("PrivatePropertyName")
    private val diskIOExecutor = Executors.newSingleThreadExecutor()

    /**
     * Inserts the response into the database while also assigning position indices to items.
     */
    private fun insertResultIntoDb(noteList: NoteListBean?) {
        noteList?.let {
            if (it.deleteInfo != null)
                when (it.deleteInfo.deleteType) {
                    DeleteType.ALL -> {
                        db.noteDao().deleteAll()
                    }
                    DeleteType.INCREMENT -> {
                        db.runInTransaction {
                            it.deleteInfo.deleteList.forEach { deletedNoteId ->
                                AWLog.e("delete note -> $deletedNoteId")
                                db.noteDao().delete(deletedNoteId)
                            }
                            db.noteDao().insert(it.list)
                        }
                    }
                }
        }
    }

    /**
     * When refresh is called, we simply run a fresh network request and when it arrives, clear
     * the database table and insert all new items in a transaction.
     * <p>
     * Since the PagedList already uses a database bound data source, it will automatically be
     * updated after the database transaction is finished.
     */
    @MainThread
    private fun refresh(): LiveData<NetworkState> {
        Log.i("TAG", "refresh: ====")
        val networkState = MutableLiveData<NetworkState>()
        networkState.value = NetworkState.LOADING
        noteListApi.loadFirstPage(networkPageSize)
                .doFinally {
                    AWLog.e("doFinally")
                }
                .asyncSubscribe({
                    diskIOExecutor.execute {
                        db.runInTransaction {
                            db.noteDao().deleteAll()
                            insertResultIntoDb(it.result)
                        }
                        AIWriteSettings.setNoteListLatestGetTime(DateUtils.getCurrentTimeMillis())
                        // since we are in bg thread now, post the result.
                        networkState.postValue(NetworkState.LOADED)
                    }
                }, {
                    networkState.value = NetworkState.error(it.message)
                })
        return networkState
    }

    /**
     * load data
     */
    @MainThread
    fun loadData(): Listing<NoteEntity> {
        Log.i("TAG", "loadData: ")
        val boundaryCallback = NoteListBoundaryCallback(
                noteListApi = noteListApi,
                handleResponse = this::insertResultIntoDb,
                ioExecutor = diskIOExecutor,
                networkPageSize = networkPageSize)
        val dataSourceFactory = db.noteDao().getNotesAsDataSource()
        val builder = LivePagedListBuilder(dataSourceFactory, networkPageSize)
                .setBoundaryCallback(boundaryCallback)

        // we are using a mutable live data to trigger refresh requests which eventually calls
        // refresh method and gets a new live data. Each refresh request by the user becomes a newly
        // dispatched data in refreshTrigger
        val refreshTrigger = MutableLiveData<Unit>()
        val refreshState = Transformations.switchMap(refreshTrigger) {
            Log.i("TAG", "refresh: ")
            refresh()
        }

        return Listing(
                totalCount = boundaryCallback.totalCount,
                pagedList = builder.build(),
                networkState = boundaryCallback.networkState,
                retry = {
                    boundaryCallback.helper.retryAllFailed()
                },
                refresh = {
                    refreshTrigger.value = null
                },
                refreshState = refreshState
        )
    }
}

